// A fancy gesture detector that implements version specific functionality
// as well as gesture recognition from most android gesture patterns.
package marco.android.CouchControl;

import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import marco.android.CouchControl.R;

public abstract class VersionedGestureDetector
{
	private static final String TAG = "VersionedGestureDetector";

	OnGestureListener mListener;

	public static VersionedGestureDetector newInstance(Context context, OnGestureListener listener)
	{
		final int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
		VersionedGestureDetector detector = null;
		if(sdkVersion < Build.VERSION_CODES.ECLAIR)
		{
			detector = new CupcakeDetector(context);
		}
		else if(sdkVersion < Build.VERSION_CODES.FROYO)
		{
			detector = new EclairDetector(context);
		}
		else
		{
			detector = new FroyoDetector(context);
		}

		Log.d(TAG, "Created new " + detector.getClass());
		detector.mListener = listener;
		return detector;
	}

	public abstract boolean onTouchEvent(MotionEvent ev);

	public interface OnGestureListener
	{
		public void onDown(float x, float y);
		
		public void onDrag(float dx, float dy);
		
		public void onUp(float x, float y);

		public void onScale(float scaleFactor, float focusX, float focusY);
		
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
		
		public boolean onDoubleTap(MotionEvent e);
		
		public void onLongPress(MotionEvent e);
	}

	private class VersionedGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener
	{
		@Override
		public boolean onDoubleTap(MotionEvent e)
		{
			return(mListener.onDoubleTap(e));
		}

		@Override
		public boolean onDoubleTapEvent(MotionEvent e)
		{
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean onSingleTapConfirmed(MotionEvent e)
		{
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean onDown(MotionEvent e)
		{
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
		{
			return(mListener.onFling(e1, e2, velocityX, velocityY));
		}

		@Override
		public void onLongPress(MotionEvent e)
		{
			mListener.onLongPress(e);		
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
		{
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public void onShowPress(MotionEvent e)
		{
		}

		@Override
		public boolean onSingleTapUp(MotionEvent e)
		{
			// TODO Auto-generated method stub
			return false;
		}
	}
	
	// 1.5
	private static class CupcakeDetector extends VersionedGestureDetector
	{
		private GestureDetector mDetector;
		float mLastTouchX;
		float mLastTouchY;
		
		public CupcakeDetector(Context context)
		{
			mDetector = new GestureDetector(context, new VersionedGestureListener());
		}

		float getActiveX(MotionEvent ev)
		{
			return ev.getX();
		}

		float getActiveY(MotionEvent ev)
		{
			return ev.getY();
		}

		boolean shouldDrag()
		{
			return true;
		}

		@Override
		public boolean onTouchEvent(MotionEvent ev)
		{
			if(mDetector.onTouchEvent(ev) == true)
			{
				return(true);
			}
			else
			{
				switch(ev.getAction())
				{
				case MotionEvent.ACTION_DOWN:
				{
					mLastTouchX = getActiveX(ev);
					mLastTouchY = getActiveY(ev);
					mListener.onDown(mLastTouchX, mLastTouchY);
					break;
				}
				case MotionEvent.ACTION_MOVE:
				{
					final float x = getActiveX(ev);
					final float y = getActiveY(ev);

					if(shouldDrag())
					{
						mListener.onDrag(x - mLastTouchX, y - mLastTouchY);
					}

					mLastTouchX = x;
					mLastTouchY = y;
					break;
				}
				case MotionEvent.ACTION_CANCEL:
				case MotionEvent.ACTION_UP:
				{
					mLastTouchX = getActiveX(ev);
					mLastTouchY = getActiveY(ev);
					mListener.onUp(mLastTouchX, mLastTouchY);
					break;
				}
				}
				return true;
			}
		}
	}

	// 2.0
	private static class EclairDetector extends CupcakeDetector
	{
		private static final int INVALID_POINTER_ID = -1;
		private int mActivePointerId = INVALID_POINTER_ID;
		private int mActivePointerIndex = 0;

		public EclairDetector(Context context)
		{
			super(context);
		}
		
		@Override
		float getActiveX(MotionEvent ev)
		{
			if(mActivePointerIndex != INVALID_POINTER_ID)
			{
			   return ev.getX(mActivePointerIndex);
			}
			
			return(ev.getX());
		}

		@Override
		float getActiveY(MotionEvent ev)
		{
			if(mActivePointerIndex != INVALID_POINTER_ID)
			{
			   return ev.getY(mActivePointerIndex);
			}

			return(ev.getY());
		}

		@Override
		public boolean onTouchEvent(MotionEvent ev)
		{
			final int action = ev.getAction();
			switch(action & MotionEvent.ACTION_MASK)
			{
			case MotionEvent.ACTION_DOWN:
				mActivePointerId = ev.getPointerId(0);
				break;
			case MotionEvent.ACTION_CANCEL:
			case MotionEvent.ACTION_UP:
				mActivePointerId = INVALID_POINTER_ID;
				break;
			case MotionEvent.ACTION_POINTER_UP:
				final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
				final int pointerId = ev.getPointerId(pointerIndex);
				if(pointerId == mActivePointerId)
				{
					// This was our active pointer going up. Choose a new
					// active pointer and adjust accordingly.
					final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
					mActivePointerId = ev.getPointerId(newPointerIndex);
					mLastTouchX = ev.getX(newPointerIndex);
					mLastTouchY = ev.getY(newPointerIndex);
				}
				break;
			}

			mActivePointerIndex = ev.findPointerIndex(mActivePointerId);
			return super.onTouchEvent(ev);
		}
	}

	// 2.2
	private static class FroyoDetector extends EclairDetector
	{
		private ScaleGestureDetector mScaleDetector;

		public FroyoDetector(Context context)
		{
			super(context);
			mScaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener()
			{
				@Override
				public boolean onScale(ScaleGestureDetector detector)
				{
					mListener.onScale(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY());
					return true;
				}
			});
		}

		@Override
		boolean shouldDrag()
		{
			return !mScaleDetector.isInProgress();
		}

		@Override
		public boolean onTouchEvent(MotionEvent ev)
		{
			mScaleDetector.onTouchEvent(ev);
			return super.onTouchEvent(ev);
		}
	}
}
